package audit

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"time"

	"chainmaker.org/chainmaker/smarteditor/conf"
	"github.com/parnurzeal/gorequest"
)

type AuditContractHttp struct {
}

const (
	apiUpload       = "/upload"
	apiAudit        = "/audit"
	apiTaskStatus   = "/task/status"
	auditLangType   = "GO"
	auditResultType = "json"
)

var (
	_ AuditContractInterface = (*AuditContractHttp)(nil)
)

type uploadData struct {
	FileId    string `json:"fileId"`
	RequestId string `json:"requestId"`
}
type wrapUploadData struct {
	Code int        `json:"code"`
	Msg  string     `json:"msg"`
	Data uploadData `json:"data"`
}

func (auditor *AuditContractHttp) UploadContractZip(zipPath, contractFileName string) (fileId, requestId string, err error) {
	uploadPath := conf.Wide.AuditServer + apiUpload
	// fileContents, err := ioutil.ReadFile(zipPath)
	// if err != nil {
	// 	log.Errorf("UploadContractZip failed %s", err.Error())
	// 	return "", "", err
	// }
	// var respData wrapUploadData
	// resp, _, errs := gorequest.New().
	// 	TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
	// 	AppendHeader("Authorization", conf.Wide.AuditAuth).
	// 	Post(uploadPath).
	// 	Type(gorequest.TypeMultipart).
	// 	SendFile(fileContents, "", "file").Timeout(5 * time.Second).
	// 	EndStruct(&respData)

	// if len(errs) > 0 || resp == nil ||
	// 	resp.StatusCode != http.StatusOK || respData.Code != http.StatusOK {
	// 	log.Errorf("UploadContractZip http error %+v,resp %+v, respData %+v, path %s",
	// 		errs, resp, respData, zipPath)
	// 	return "", "", errors.New("UploadContractZip failed")
	// }
	resp, err := transportFile(uploadPath, zipPath, contractFileName)
	if err != nil || resp == nil || resp.Code != http.StatusOK {
		log.Errorf("UploadContractZip http error %+v,resp %+v", err, resp)
		return "", "", errors.New("UploadContractZip failed")
	}
	return resp.Data.FileId, resp.Data.RequestId, nil
}

type auditRequest struct {
	FileId     string `json:"fileId"`
	TaskName   string `json:"taskName"`
	LangType   string `json:"langType"`
	ResultType string `json:"resultType"`
}
type auditData struct {
	TaskId    string `json:"taskId"`
	RequestId string `json:"requestId"`
}
type wrapAuditData struct {
	Code int       `json:"code"`
	Msg  string    `json:"msg"`
	Data auditData `json:"data"`
}

func (auditor *AuditContractHttp) AuditUseFileId(fileId string) (taskId, requestId string, err error) {
	auditPath := conf.Wide.AuditServer + apiAudit
	req := auditRequest{
		FileId:     fileId,
		TaskName:   strconv.FormatInt(time.Now().UnixNano(), 10),
		LangType:   auditLangType,
		ResultType: auditResultType,
	}
	var auditResp wrapAuditData
	resp, _, errs := gorequest.New().
		TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
		AppendHeader("Authorization", conf.Wide.AuditAuth).
		Type(gorequest.TypeJSON).
		Post(auditPath).SendStruct(&req).Timeout(5 * time.Second).EndStruct(&auditResp)
	if len(errs) > 0 || resp == nil ||
		resp.StatusCode != http.StatusOK ||
		auditResp.Code != http.StatusOK {
		log.Errorf("AuditUseFileId http error %+v,resp %+v, auditResp %+v ,fileId %s",
			errs, resp, auditResp, fileId)
		return "", "", errors.New("AuditUseFileId failed")
	}
	return auditResp.Data.TaskId, auditResp.Data.RequestId, nil
}

type taskRequest struct {
	TaskId string `json:"task_id" binding:"required"`
}

type wrapTaskResult struct {
	Code int       `json:"code"`
	Msg  string    `json:"msg"`
	Data *TaskData `json:"data"`
}

func (auditor *AuditContractHttp) QueryAuditStatusByTaskId(taskId, userSrcPath string) (retD *TaskData, err error) {
	queryPath := conf.Wide.AuditServer + apiTaskStatus
	req := taskRequest{
		TaskId: taskId,
	}
	var queryResp wrapTaskResult
	resp, _, errs := gorequest.New().
		TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
		AppendHeader("Authorization", conf.Wide.AuditAuth).
		Type(gorequest.TypeJSON).Post(queryPath).
		SendStruct(&req).Timeout(5 * time.Second).EndStruct(&queryResp)
	if len(errs) > 0 || resp == nil ||
		resp.StatusCode != http.StatusOK ||
		queryResp.Code != http.StatusOK {
		log.Errorf("QueryAuditStatusByTaskId http error %+v,resp %+v,queryResp %+v , taskId %s",
			errs, resp, queryResp, taskId)
		return nil, errors.New("QueryAuditStatusByTaskId failed")
	}
	if queryResp.Data != nil && len(queryResp.Data.Result) > 0 {
		for i := 0; i < len(queryResp.Data.Result); i++ {
			queryResp.Data.Result[i].FilePath = generateNewFilePath(queryResp.Data.Result[i].File,
				userSrcPath, conf.Wide.AuditFilePrefix)
		}
	}
	return queryResp.Data, nil
}

func generateNewFilePath(retPath, userSrcPath, prefix string) string {
	// residu := strings.TrimPrefix(retPath, prefix)
	// splits := strings.SplitN(residu, "/", 2)
	// if len(splits) != 2 {
	// 	return residu
	// }
	// return filepath.Join(userSrcPath, splits[1])
	return filepath.Join(userSrcPath, retPath)
}

func transportFile(targetUrl string, filePath, fileName string) (*wrapUploadData, error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("file", fileName)
	if err != nil {
		log.Error("error writing to buffer")
		return nil, err
	}
	opend, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer opend.Close()
	stat, err := opend.Stat()
	if err != nil {
		return nil, err
	}
	if stat == nil {
		return nil, errors.New("stat failed")
	}
	_, err = io.Copy(fileWriter, opend)
	if err != nil {
		return nil, err
	}

	err = bodyWriter.Close()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest(http.MethodPost, targetUrl, bodyBuf)
	req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
	req.Header.Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
	req.Header.Set("Authorization", conf.Wide.AuditAuth)
	// 获取转发到的服务的响应
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp == nil || resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("resp %+v", resp)
	}
	// 处理响应
	defer resp.Body.Close()
	bys, bysErr := ioutil.ReadAll(resp.Body)
	if bysErr != nil {
		return nil, bysErr
	}
	var retD wrapUploadData
	unmarshalErr := json.Unmarshal(bys, &retD)
	if unmarshalErr != nil {
		return nil, unmarshalErr
	}
	return &retD, nil
}
